package com.ruyuan.payment.server.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ruyuan.payment.server.domain.*;
import com.ruyuan.payment.server.enums.AccountLogTypeEnum;
import com.ruyuan.payment.server.enums.FreezeLogStatusEnum;
import com.ruyuan.payment.server.exception.BusinessException;
import com.ruyuan.payment.server.exception.BusinessExceptionEnum;
import com.ruyuan.payment.server.mapper.AccountLogMapper;
import com.ruyuan.payment.server.mapper.AccountMapper;
import com.ruyuan.payment.server.mapper.FreezeLogMapper;
import com.ruyuan.payment.server.req.AccountQueryReq;
import com.ruyuan.payment.server.resp.AccountQueryResp;
import com.ruyuan.payment.server.resp.PageResp;
import com.ruyuan.payment.server.util.CopyUtil;
import com.ruyuan.payment.server.util.SnowFlake;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Service
public class AccountService {

    private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);

    @Resource
    private AccountMapper accountMapper;

    @Resource
    private AccountLogMapper accountLogMapper;

    @Resource
    private FreezeLogMapper freezeLogMapper;

    @Resource
    private SnowFlake snowFlake;

    public void add(Long memberId) {
        Date now = new Date();
        Account account = new Account();
        account.setId(snowFlake.nextId());
        account.setMemberId(memberId);
        account.setAmount(BigDecimal.ZERO);
        account.setFreezeAmount(BigDecimal.ZERO);
        account.setVersion(0);
        account.setCreatedTime(now);
        account.setUpdatedTime(now);
        accountMapper.insert(account);
    }

    @Transactional
    public void doAccount(Long memberId, BigDecimal amount, String desc) {
        LOG.info("变更账户余额开始 memberId {}, amount {}", memberId, amount);
        Date now = new Date();

        // 基础校验：存在性校验，amount!=0
        if (amount.compareTo(BigDecimal.ZERO) == 0) {
            LOG.error("入账金额为0！");
            throw new BusinessException(BusinessExceptionEnum.DO_ACCOUNT_ERROR);
        }

        Account accountDB = selectByMemberId(memberId);
        if (ObjectUtils.isEmpty(accountDB)) {
            LOG.error("账户不存在！memberId: {}", memberId);
            throw new BusinessException(BusinessExceptionEnum.DO_ACCOUNT_ERROR);
        }

        // 变更后的余额校验
        BigDecimal oldAmount = accountDB.getAmount();
        BigDecimal newAmount = oldAmount.add(amount);
        if (newAmount.compareTo(BigDecimal.ZERO) < 0) {
            LOG.error("计算变更后的余额小于0，入账失败！memberId: {}，oldAmount: {}，newAmount: {}", memberId, oldAmount, newAmount);
            throw new BusinessException(BusinessExceptionEnum.DO_ACCOUNT_ERROR);
        }

        // 变更后的余额不能小于冻结金额
        if (newAmount.compareTo(accountDB.getFreezeAmount()) < 0) {
            LOG.error("计算变更后的余额小于冻结金额，入账失败！memberId: {}，freezeAmount: {}，newAmount: {}", memberId, accountDB.getFreezeAmount(), newAmount);
            throw new BusinessException(BusinessExceptionEnum.DO_ACCOUNT_ERROR);
        }

        LOG.info("变更后的余额：{}", newAmount);

        // try {
        //     Thread.sleep(20000);
        // } catch (InterruptedException e) {
        //     e.printStackTrace();
        // }

        accountDB.setAmount(newAmount);
        LOG.info("更新账户，以版本号为乐观锁 memberId {}, amount {}", memberId, amount);
        if (updateByVersion(now, accountDB) == 0) {
            LOG.error("更新账户失败，乐观锁失效！account: {}", accountDB);
            throw new BusinessException(BusinessExceptionEnum.DO_ACCOUNT_ERROR);
        }

        LOG.info("增加变更记录开始 memberId {}, amount {}", memberId, amount);
        AccountLog accountLog = new AccountLog();
        accountLog.setId(snowFlake.nextId());
        accountLog.setMemberId(memberId);
        accountLog.setAccountId(accountDB.getId());
        if (amount.compareTo(BigDecimal.ZERO) > 0) {
            accountLog.setType(AccountLogTypeEnum.IN.getCode());
        } else {
            accountLog.setType(AccountLogTypeEnum.OUT.getCode());
        }
        accountLog.setTime(now);
        accountLog.setAmountBefore(oldAmount);
        accountLog.setAmount(amount);
        accountLog.setAmountAfter(newAmount);
        accountLog.setDesc(desc);
        accountLogMapper.insert(accountLog);

        LOG.info("变更账户余额结束 memberId {}, amount {}", memberId, amount);
    }

    public AccountQueryResp queryOne(Long memberId) {
        Account account = selectByMemberId(memberId);
        return CopyUtil.copy(account, AccountQueryResp.class);
    }

    private Account selectByMemberId(Long memberId) {
        AccountExample accountExample = new AccountExample();
        accountExample.createCriteria().andMemberIdEqualTo(memberId);
        List<Account> list = accountMapper.selectByExample(accountExample);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        } else {
            return list.get(0);
        }
    }

    public PageResp<AccountQueryResp> queryList(AccountQueryReq req) {
        Long memberId = req.getMemberId();
        AccountExample accountExample = new AccountExample();
        AccountExample.Criteria criteria = accountExample.createCriteria();
        if (!ObjectUtils.isEmpty(memberId)) {
            criteria.andMemberIdEqualTo(memberId);
        }
        accountExample.setOrderByClause("id desc");

        LOG.info("查询页码：{}", req.getPage());
        LOG.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<Account> accountList = accountMapper.selectByExample(accountExample);

        PageInfo<Account> pageInfo = new PageInfo<>(accountList);
        LOG.info("总行数：{}", pageInfo.getTotal());
        LOG.info("总页数：{}", pageInfo.getPages());

        List<AccountQueryResp> queryRespList = CopyUtil.copyList(accountList, AccountQueryResp.class);

        PageResp<AccountQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(queryRespList);

        return pageResp;
    }

    @Transactional
    public void freeze(Long memberId, BigDecimal amount, String payNo) {
        LOG.info("冻结账户余额开始 memberId {}, amount {}", memberId, amount);
        Date now = new Date();

        // 基础校验：存在性校验，amount!=0
        if (amount.compareTo(BigDecimal.ZERO) == 0) {
            LOG.error("冻结金额为0！");
            throw new BusinessException(BusinessExceptionEnum.FREEZE_ERROR);
        }

        Account accountDB = selectByMemberId(memberId);
        if (ObjectUtils.isEmpty(accountDB)) {
            LOG.error("账户不存在！memberId: {}", memberId);
            throw new BusinessException(BusinessExceptionEnum.FREEZE_ERROR);
        }

        // 冻结金额校验
        // 可用余额 = 账户金额 - 已冻结金额
        BigDecimal availableAmount = accountDB.getAmount().subtract(accountDB.getFreezeAmount());
        if (availableAmount.compareTo(amount) < 0) {
            LOG.error("账户可用金额小于冻结金额，冻结失败！memberId: {}，账户余额: {}，账户冻结余额: {}，本次冻结: {}", memberId, accountDB.getAmount(), accountDB.getFreezeAmount(), amount);
            throw new BusinessException(BusinessExceptionEnum.FREEZE_ERROR);
        }

        // 每次冻结后的冻结总金额 = 当前已冻结 + 本次新冻结金额
        BigDecimal totalFreezeAmount = accountDB.getFreezeAmount().add(amount);
        accountDB.setFreezeAmount(totalFreezeAmount);
        LOG.info("冻结账户，以版本号为乐观锁 memberId {}, amount {}", memberId, amount);
        if (updateByVersion(now, accountDB) == 0) {
            LOG.error("冻结账户失败，乐观锁失效！account: {}", accountDB);
            throw new BusinessException(BusinessExceptionEnum.FREEZE_ERROR);
        }

        LOG.info("增加冻结记录开始 memberId {}, amount {}", memberId, amount);
        FreezeLog freezeLog = new FreezeLog();
        freezeLog.setId(snowFlake.nextId());
        freezeLog.setMemberId(memberId);
        freezeLog.setAccountId(accountDB.getId());
        freezeLog.setPayNo(payNo);
        freezeLog.setTime(now);
        freezeLog.setAmount(amount);
        freezeLog.setStatus(FreezeLogStatusEnum.FREEZE.getCode());
        freezeLog.setCreatedTime(now);
        freezeLog.setUpdatedTime(now);
        freezeLogMapper.insert(freezeLog);

        LOG.info("冻结账户余额结束 memberId {}, amount {}", memberId, amount);
    }

    private int updateByVersion(Date now, Account accountDB) {
        AccountExample accountExample = new AccountExample();
        accountExample.createCriteria()
                .andIdEqualTo(accountDB.getId())
                .andVersionEqualTo(accountDB.getVersion());
        accountDB.setVersion(accountDB.getVersion() + 1);
        accountDB.setUpdatedTime(now);
        return accountMapper.updateByExample(accountDB, accountExample);
    }

    @Transactional
    public void unfreeze(Long memberId, BigDecimal unFreezeAmount, String payNo) {
        LOG.info("解冻账户开始 memberId {}, unFreezeAmount {}, payNo {}", memberId, unFreezeAmount, payNo);
        Date now = new Date();

        // 基础校验：存在性校验，unFreezeAmount!=0
        if (unFreezeAmount.compareTo(BigDecimal.ZERO) == 0) {
            LOG.error("解冻金额为0！");
            throw new BusinessException(BusinessExceptionEnum.UNFREEZE_ERROR);
        }

        Account accountDB = selectByMemberId(memberId);
        if (ObjectUtils.isEmpty(accountDB)) {
            LOG.error("账户不存在！memberId: {}", memberId);
            throw new BusinessException(BusinessExceptionEnum.UNFREEZE_ERROR);
        }

        // 变更后的余额校验
        BigDecimal oldFreezeAmount = accountDB.getFreezeAmount();
        BigDecimal newFreezeAmount = oldFreezeAmount.subtract(unFreezeAmount);
        if (newFreezeAmount.compareTo(BigDecimal.ZERO) < 0) {
            LOG.error("计算变更后的冻结金额小于0，解冻失败！memberId: {}，oldFreezeAmount: {}，newFreezeAmount: {}", memberId, oldFreezeAmount, newFreezeAmount);
            throw new BusinessException(BusinessExceptionEnum.UNFREEZE_ERROR);
        }

        LOG.info("变更后的冻结金额：{}", newFreezeAmount);

        FreezeLog freezeLogDB = selectFreezeLogByPayNo(payNo);
        LOG.info("冻结流水：{}", freezeLogDB);
        if (ObjectUtils.isEmpty(freezeLogDB) || freezeLogDB.getStatus().equals(FreezeLogStatusEnum.UNFREEZE.getCode())) {
            LOG.error("冻结记录不存在，或已解冻");
            throw new BusinessException(BusinessExceptionEnum.UNFREEZE_ERROR);
        }

        // try {
        //     Thread.sleep(20000);
        // } catch (InterruptedException e) {
        //     e.printStackTrace();
        // }

        accountDB.setFreezeAmount(newFreezeAmount);
        LOG.info("更新账户冻结金额，以版本号为乐观锁 memberId {}, unFreezeAmount {}", memberId, unFreezeAmount);
        if (updateByVersion(now, accountDB) == 0) {
            LOG.error("更新账户冻结金额失败，乐观锁失效！account: {}", accountDB);
            throw new BusinessException(BusinessExceptionEnum.UNFREEZE_ERROR);
        }

        // 解冻冻结流水
        freezeLogDB.setStatus(FreezeLogStatusEnum.UNFREEZE.getCode());
        freezeLogDB.setUpdatedTime(now);
        freezeLogMapper.updateByPrimaryKey(freezeLogDB);

        LOG.info("解冻账户结束 memberId {}, unFreezeAmount {}", memberId, unFreezeAmount);
    }

    @Transactional
    public void unfreezeAndDoAccount(Long memberId, BigDecimal accountAmount, String payNo) {
        LOG.info("解冻并扣款开始 memberId {}, accountAmount {}, payNo {}", memberId, accountAmount, payNo);

        // 解冻
        unfreeze(memberId, accountAmount, payNo);

        // 扣款
        doAccount(memberId, BigDecimal.ZERO.subtract(accountAmount), "解冻并扣款，支付订单号：" + payNo);

        LOG.info("解冻并扣款结束 memberId {}, accountAmount {}, payNo {}", memberId, accountAmount, payNo);
    }

    public FreezeLog selectFreezeLogByPayNo(String payNo) {
        FreezeLogExample freezeLogExample = new FreezeLogExample();
        freezeLogExample.createCriteria().andPayNoEqualTo(payNo);
        List<FreezeLog> list = freezeLogMapper.selectByExample(freezeLogExample);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        } else {
            return list.get(0);
        }
    }
}
